package com.hotfix;

import android.app.Application;
import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import dalvik.system.PathClassLoader;

import android.content.Context;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class HotFixManager {
    public HotFixManager() {
    }

    public void fix(Application application) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        // 1. 获取pathList 对象
        // 1.1拿到类加载器对象
        PathClassLoader pathClassLoader = (PathClassLoader) application.getClassLoader();
        // 1.2 pathList载pathClassLoader的父类BaseDexClassLoader中，获取BaseDexClassLoader
        Class pathClazz = pathClassLoader.getClass().getSuperclass();
        // 1.3 拿到成员变量pathList
        Field field = pathClazz.getDeclaredField("pathList");
        // 1.4 pathList有private修饰，开放访问限制。
        field.setAccessible(true);
        // 1.5 通过类加载器，获取类加载中缓存的pathList对象。
        Object pathList = field.get(pathClassLoader);
        if (pathList == null) {
            return;
        }
        // 2. 获取pathList中dexElements对象
        // 2.1 获取pathList class
        Class pathListClazz = pathList.getClass();
        // 2.2 获取成员变量dexElements
        Field dexElementsField = pathListClazz.getDeclaredField("dexElements");
        // 2.3 开放成员变量dexElements的访问限制。
        dexElementsField.setAccessible(true);
        // 2.4 获取dexElements对象。
        Object[] dexElements = (Object[]) dexElementsField.get(pathList);
        // 3. 获取插件中的elements
        // 3.1 反射pathList中的makePathElements方法，加载patch插件
        Method method = pathListClazz.getDeclaredMethod("makePathElements", List.class, File.class, List.class);
        // 3.2 makePathElements需要3个参数，参数1，插件文件files对象。
        List<File> files = new ArrayList<>();
        // 3.2.1 插件文件
        File patchFile = new File(application.getFilesDir(), "patch.jar");
        // 3.2.2 给插件文件设置为只读（android 14要求，仅运行读取只读的dex，否则会报错：Attempt to load writable dex file。
        patchFile.setReadOnly();
        files.add(patchFile);
        // 3.3 参数2:创建一个用于保存odex文件的文件夹。
        File optimizeFold = new File(application.getFilesDir(), "odex/");
        if (!optimizeFold.exists()) {
            optimizeFold.mkdirs();
        }
        // 3.4 参数3:创建一个异常接收列表。
        List<IOException> exceptionList = new ArrayList<>();
        // 3.5 放开makePathElements的访问限制
        method.setAccessible(true);
        // 3.6 获取到补丁中的 dexElements
        Object[] newDexElements = (Object[]) method.invoke(null, files, optimizeFold, exceptionList);
        // 4.1 创建一个新的elements数组，用于合并补丁和原始的elements。
        Object[] elements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(), dexElements.length + newDexElements.length);
        // 4.2 将补丁放到新数组的最前面，使其首个加载。
        System.arraycopy(newDexElements, 0, elements, 0, newDexElements.length);
        // 4.3 将原始的elements放到新数组中。
        System.arraycopy(dexElements,0, elements, newDexElements.length, dexElements.length);
        // 4.4 使用新数组将pathList中的原始数组替换过来。
        dexElementsField.set(pathList, elements);
        Log.e("zxh","热修复完成");
    }
}
